package gbench.sandbox.data;

import org.junit.jupiter.api.Test;

import static gbench.common.matlib.MatlibCanvas.println;
import static gbench.common.matlib.algebra.lisp.MyRecord.REC;
import static gbench.common.matlib.algebra.num4j.PmCm.L;
import static gbench.common.tree.LittleTree.Term.FT;
import static gbench.sandbox.data.index.Algorithm.KEY_VALUE;
import static gbench.sandbox.data.index.TableSearchEngine.KTB;

import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

import gbench.common.matlib.algebra.lisp.IRecord;
import gbench.common.matlib.algebra.lisp.MyRecord;
import gbench.common.matlib.algebra.lisp.Tuple2;
import gbench.common.matlib.algebra.num4j.PmCm;
import gbench.common.matlib.data.DataReader.DFrame;
import gbench.common.tree.LittleTree;
import gbench.common.tree.LittleTree.CronTime;
import gbench.common.tree.LittleTree.ExceptionalFunction;
import gbench.common.tree.LittleTree.SQL;
import gbench.common.tree.LittleTree.Jdbc.XJdbcSession;
import gbench.common.tree.LittleTree.Json;
import gbench.commonApp.data.DataApp;
import gbench.sandbox.data.index.TableSearchEngine;

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;

/**
 * 
 * @author Administrator
 *
 */
public class JunitTableSearchEngine2 {

    /**
     * 简单的数据引用
     */
    @Test
    public void foo() {

        // 系统初始化准备
        this.initialize();
        // 租户系统初始化
        this.tenant_init();

        // 租户系统订单模拟
        for (var tenant : this.tenants) {
            this.handle(tenant, 100, 10);
        }
    }

    /**
     * 功能函数的测试
     */
    @Test
    public void bar() {
        final IRecord aa = REC("a", L("a1", L(REC("a11", "a111"), REC("a12", L(1, 2 + "", 3, 4)))));
        println(aa.toString(e -> e, ",", "{", "}", ",", "[", "]", ":", "\"", "\"", v -> v + ""));
        final var rec = REC("name", "zhangsan", "obj", REC("city", "shanghai", "x", REC("street", "fahuazhen")));
        println(rec);
        println(aa.json());
    }

    /**
     * 店铺初始化
     */
    public void tenant_init() {
        dataApp.withTransactionX(DFrame.dfmclc, sess -> {
            final var tenant_sql = FT("select * from t_vertex where json_contains(json_array($0),Attributes->'$.tags')",
                    joiner.apply(L("店铺")));
            final var dfm = sess.sql2u(tenant_sql);
            final var sqls = dfm.rowsS().map(e -> {
                final var name = e.str("name"); // 名称
                final var pinyin = pinyin(name); // 拼音
                final var home = FT("http://localhost:8089/$0", pinyin); // 主页
                final var jdbc_url = FT(
                        "jdbc:mysql://localhost:3306/$0?useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=True",
                        pinyin);
                final var config = REC( // 站点配置
                        "home", home, "title", name, "description", name, // 摘要信息
                        "database", REC("url", jdbc_url, "user", "root", "password", "123456") // 数据库配置
                ); // 站点配置信息
                final var sql = FT("update t_vertex set Attributes=JSON_SET(Attributes,'$.config',$0) where id=$1",
                        JSON_OBJECT(config), e.i4("id")); // 为站点添加属性信息
                return sql;
            }).toArray(String[]::new); // sqls

            for (var sql : sqls) {
                println(sess.sqlexecute(sql), sql);
            }
        });
    }

    /**
     * 创建订单数据，交易&交易记录的生成
     * 
     * @param tenant        创建主体，创建订单的租户系统名称。
     * @param times         模拟创建订单的次数
     * @param max_bill_size 订单的最大项目个数
     */
    public void handle(final String tenant, int times, int max_bill_size) {

        final var encoder = srchEngne.getEncoder(KTB("name,drcr,v0,v1,tenant_id,parta_id,partb_id:t_vertex")); // name索引检索,提取id部分。
        final var encoder2 = srchEngne.getEncoder(e -> e, KTB("name,drcr,v0,v1:t_vertex")); // name索引检索,读取完整明细
        final var randpct = rndget.apply(products); // 随机提取产品
        final var randtenant = rndget.apply(tenants); // 随机提取用户

        final var order_entity_rb = LittleTree.IRecord.rb("name,label,tags,createtime".split(",")); // 订单的创建器
        final var attrs_rb = IRecord.rb("name,alias,tags,code,_order_id,createtime".split(",")); // 订单属性创建器
        final var edge_rb = IRecord.rb("Relation,v0,v1,Attributes,createtime".split(",")); // 边创建器
        final var amt_rb = IRecord.rb("_price,_quantity,_amount"); // 金额3字段

        // 记账法
        final Function<String, Function<String[], Stream<IRecord>>> double_entry_method = drcr -> names -> Stream
                .of(names).map(name -> IRecord.rb("drcr,name").get(drcr, name)); // 复式记账法
        // 订单/交易构建器
        final Function<Tuple2<Long, String>, Function<Tuple2<IRecord, ? extends Number>, IRecord>> order_product_builder = ( // (code,id)
                tuple_order) -> (tuple_product) -> { // (product,quantity)
                    final var product = tuple_product._1(); // 产品
                    final var quantity = tuple_product._2.doubleValue(); // 产品数量
                    final var name = product.str("name");
                    final var profile = encoder2.apply(product); // 产品摘要

                    final var pctid = profile.pathint("name/id"); // 产品id
                    final var price = profile.pathget("name/Attributes/price", map_parser, IRecord.obj2dbl()); // 产品价格
                    final var amount = price * quantity; // 金额
                    final var tags = "订单行项目,TX".split(","); // 标签

                    final var _order_id = tuple_order._1; // 订单id,交易id,id 是检索的主键含义，类似于身份证号码
                    final var code = tuple_order._2; // 订单编码,交易码,code 是 逻辑含义，类似于 江湖外号，比如及时雨宋江。
                    final var attrs = attrs_rb.get(name, pinyin(name), tags, code, _order_id, now())
                            .derive(amt_rb.get(price, quantity, amount)) // 加入金额三字段
                            .derive("_order_title", code, "pctid", pctid); // 加入订单标题

                    final var r = edge_rb.get(code, product.get("drcr"), product.get("name"), attrs, now()); // 创建边

                    return r;
                }; // 订单交易构建器,订单行项目的生成逻辑

        dataApp.withTransactionX(DFrame.dfmclc, sess -> { // 数据库操作

            final var vertex_persister = persister.apply(sess); // 数据持久化函数

            for (int i = 0; i < times; i++) { // 创建订单记录

                final var code = System.nanoTime() + ""; // 订单代码

                final var b = Math.random() > 0.5; // 投掷色子, 明确 甲，乙身份
                final var counterpart = randtenant.get(); // 对手方
                final var parta = b ? tenant : counterpart; // 甲方
                final var partb = b ? counterpart : tenant; // 乙方
                final var order_profile = REC( // 订单摘要
                        "tenant_id", tenant, // 主体方id 即 创建订单平台id，系统id
                        "tenant", tenant, // 主体方 即 创建订单平台名称,系统名称
                        "parta_id", parta, "parta", parta, // 甲方信息
                        "partb_id", partb, "partb", partb // 乙方信息
                ); // 订单信息
                final var ltrec = encoder.apply(order_profile).mutate(e -> LP2LT(e));
                final var order_entity = order_entity_rb.get(code, "ORDER", "订单".split(","), now()) // 生成交易对象
                        .derive(ltrec); // 数据配置
                final var _order_id = vertex_persister.apply(order_entity).longValue(); // 交易对象的写入

                final var pcts = Stream.iterate(0, k -> k < rand.nextInt(max_bill_size), k -> k + 1)
                        .map(k -> randpct.get()) // 随机获取产品
                        .toArray(String[]::new); // 产品名单

                if (pcts.length < 1) {
                    continue;
                } // if

                final var statkeys = "pctid,name,_price,_quantity,_amount"; // 统计结构
                final var statlines = new LinkedList<IRecord>(); // 统计数据行
                final var orderline_builder = order_product_builder.apply(Tuple2.TUP2(_order_id, code)); // 构造一个订单行项目的生成器

                // 借方记账
                final var drcr_entries = double_entry_method.apply("借").apply(pcts) // 借方科目列表
                        .map(e -> Tuple2.TUP2(e, rand.nextInt(100))) // 产品数量二元组
                        .map(orderline_builder).peek(orderline -> {
                            final var attrs = orderline.rec("Attributes"); // 提取行项目属性
                            statlines.add(attrs.filter(statkeys)); // 录入统计数据
                            orderline.add("Attributes", attrs.json());
                        }).collect(Collectors.toList()); // 订单行项目-借方

                // 借方数据统计
                final var statdfm = statlines.stream().collect(gbench.common.matlib.algebra.lisp.DFrame.dfmclc); // 统计数据框

                // 贷方记账
                double_entry_method.apply("贷").apply(PmCm.A("银行存款")) // 贷方科目列表
                        .forEach(acct -> { // 贷方科目的处理
                            final var attrs_json = IRecord.rb("name,tags,_quantity,_amount")
                                    .get(acct.str("name"), "财务项目,TX".split(","), statdfm.sum("_quantity"),
                                            statdfm.sum("_amount")) // 生成属性主体
                                    .derive("label", "ORDER", "_order_id", _order_id, "_order_title", code, "createtime", now()) // 加入标签项目
                                    .mutate(e -> e.json());
                            final var r = edge_rb.get(code, acct.get("drcr"), acct.get("name"), attrs_json, now()); // 创建边
                            drcr_entries.add(r);
                        }); // 订单行项目-贷方

                final var orderline_sqls = drcr_entries.stream().map(encoder) // 订单行的数据插入数据的 sql
                        .map(e -> SQL.of("t_edge", LittleTree.IRecord.REC(e.toMap())).insert()) // 生成sql语句
                        .toArray(String[]::new); // order_line 插入的sql语句

                final var item_ids = new ArrayList<Integer>();
                for (var sql : orderline_sqls) { // sql 执行
                    final var item_id = sess.sql2execute2int(sql); // 插入分录数据
                    item_ids.add(item_id);
                    println(item_id, sql);
                } // for

                final var update_sql = FT("update t_vertex set Attributes=JSON_SET(Attributes,"
                        + "'$.names',JSON_ARRAY($0)," + "'$.prices',JSON_ARRAY($1)," + "'$.quantities',JSON_ARRAY($2),"
                        + "'$.amounts',JSON_ARRAY($3)," + "'$.item_ids',JSON_ARRAY($4),"
                        + "'$.total',$5,'$.amounts_chart','~_.zip(names,amounts).map(kvpa)'" + ") where id = $6",
                        joiner.apply(statdfm.col("name")), joiner.apply(statdfm.col("_price")),
                        joiner.apply(statdfm.col("_quantity")), joiner.apply(statdfm.col("_amount")),
                        joiner.apply(item_ids), statdfm.sum("_amount"), _order_id);

                sess.sqlexecute(update_sql); // 数据更新

            } // for 创建订单

        }); // withTransaction

    }

    /**
     * 数据初始化
     */
    @Test
    public void initialize() {

        final var individual_rb = LittleTree.IRecord.rb("name,alias,en,tags,label,createtime".split(","));
        final var acct_rb = LittleTree.IRecord.rb("name,alias,en,tags,label,createtime".split(","));
        final var product_rb = LittleTree.IRecord.rb("name,alias,en,tags,price,createtime".split(","));
        final var tenant_rb = LittleTree.IRecord.rb("name,alias,en,tags,label,createtime".split(","));

        dataApp.withTransactionX(DFrame.dfmclc, sess -> {
            final var vertex_getter = persister.apply(sess);

            // 人
            for (var name : staff) { // 人员创建
                final var individual = individual_rb.get(name, pinyin(name), pinyin(name), "人员".split(","), "staff",
                        now());
                println(name, vertex_getter.apply(individual));
            }

            // 财
            for (var name : accts) { // 财务科目创建
                final var acct = acct_rb.get(name, pinyin(name), pinyin(name), "财务".split(","), "acct", now());
                println(name, vertex_getter.apply(acct));
            }

            // 物
            for (var name : products) { // 产品科目创建
                final var product = product_rb.get(name, pinyin(name), pinyin(name), "水果".split(","), rand.nextInt(100),
                        now());
                println(name, vertex_getter.apply(product));
            }

            // 交易的空间场所
            for (var name : tenants) { // 店铺创建
                final var tenant = tenant_rb.get(name, pinyin(name), pinyin(name), "店铺".split(","), "tenant", now());
                println(name, vertex_getter.apply(tenant));
            }

        });
    }

    /**
     * 获取UUID
     * 
     * @return
     */
    public TableSearchEngine getSearchEngine() {
        final var srchEngne = new TableSearchEngine();
        srchEngne.setConfig(config); // 数据库配置
        srchEngne.addAlgorithm(KEY_VALUE).addIndexEngine("t_vertex", (x, y) -> "id,name".split(",")); // 创建索引引擎
        return srchEngne;
    }

    /**
     * 获取拼音结构
     * 
     * @param line 文本数据
     * @return 文本的拼音
     */
    public static String pinyin(final String line) {
        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
        // 拼音小写
        format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        // 不带声调
        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        // 要转换的中文，格式，转换之后的拼音的分隔符，遇到不能转换的是否保留 wo,shi,zhong,guo,ren,，hello
        var pingyin = "-";
        try {
            pingyin = PinyinHelper.toHanYuPinyinString(line, format, "", true);
        } catch (BadHanyuPinyinOutputFormatCombination e) {
            e.printStackTrace();
        }

        return pingyin;
    }

    /**
     * 当前系统时间
     * 
     * @return 系统时间
     */
    public static String now() {
        return LocalDateTime.now().format(DTF);
    }

    /**
     * 格式化 record
     * 
     * @param ltrec
     * @return json_object格式的record
     */
    public static String JSON_OBJECT(final LittleTree.IRecord ltrec) {
        return JSON_OBJECT(new MyRecord(ltrec.toMap()));
    }

    /**
     * 格式化 record
     * 
     * @param ltrec
     * @return json_object格式的record
     */
    public static String JSON_OBJECT(final IRecord lprec) {
        return lprec.toString(value -> {
            if (value instanceof Map) {
                return new MyRecord((Map<?, ?>) value);
            } else if (value instanceof LittleTree.IRecord) {
                return new MyRecord(((LittleTree.IRecord) value).toMap());
            } else {
                return value;
            }
        }, ",", "JSON_OBJECT(", ")", ",", "[", "]", ",", "\"", "\"");
    }

    /**
     * Lisp IRecord 转 LittleTree IRecord
     * 
     * @param lprec Lisp IRecord
     * @return LittleTree IRecord
     */
    public static gbench.common.tree.LittleTree.IRecord LP2LT(final gbench.common.matlib.algebra.lisp.IRecord lprec) {
        return gbench.common.tree.LittleTree.IRecord.REC(lprec.toMap());
    }

    /**
     * LittleTree IRecord 转 Lisp IRecord
     * 
     * @param ltrec LittleTree IRecord
     * @return Lisp Record
     */
    public static gbench.common.matlib.algebra.lisp.IRecord LT2LP(final gbench.common.tree.LittleTree.IRecord ltrec) {
        return gbench.common.matlib.algebra.lisp.MyRecord.REC(ltrec.toMap());
    }

    /**
     * 主体数据
     */
    private final String[] staff = "张珊,李丝,王舞,赵柳,陈琪,孙芭,戴究".split(","); // 人员
    private final String[] accts = "借,贷,银行存款".split(","); // 财务科目
    private final String[] products = "苹果,草莓,鸭梨,哈密瓜,葡萄,芒果,水蜜桃,西瓜,土豆,番茄,牛肉,羊肉,驴肉,马肉,鸡肉,带鱼,马鲛鱼,金枪鱼".split(",");
    private final String[] tenants = "北京店,天津店,上海店,重庆店,广州店,沈阳店,大连店,郑州店,宁波店,成都店,拉萨店,乌鲁木齐店,兰州店,香港店,台湾店,海口店".split(","); // 店铺科目

    /**
     * 数据库配置
     */
    private final IRecord config = REC("url",
            "jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=True");
    /**
     * 数据库操作对象
     */
    private final DataApp dataApp = new DataApp() {
    }.reload2(LittleTree.IRecord.REC(config.toMap())); // 数据库操作对象

    /**
     * 表检索结结构
     */
    private final TableSearchEngine srchEngne = this.getSearchEngine();

    /**
     * 随机函数
     */
    private static final Function<String[], Supplier<String>> rndget = aa -> () -> aa[new Random().nextInt(aa.length)];

    /**
     * IRecord 值预处理函数
     */
    private static final BiFunction<String, Object, Object> map_parser = (k, e) -> {
        if (e instanceof String && ((String) e).charAt(0) == '{') {
            final var map = Json.json2map(e);
            if (map != null) {
                return map;
            } else {
                return e;
            }
        } else {
            return e;
        }
    };

    /**
     * 静态成员
     */
    private static final Random rand = new Random(); // 随机函数

    /**
     * 时间格式化器
     */
    private static final DateTimeFormatter DTF = CronTime.dtf("yyyy-MM-dd HH:mm:ss");

    /**
     * uuid
     * 
     * @return uuid
     */
    public static final String uuid() {
        return new BigInteger(UUID.randomUUID().toString().replaceAll("-", ""), 16).toString(Character.MAX_RADIX);
    }

    /**
     * 连接器
     */
    private static final Function<List<?>, String> joiner = oo -> oo.stream()
            .map(e -> FT(e instanceof String ? "'$0'" : "$0", e)).collect(Collectors.joining(","));

    /**
     * vertex 的订单持久化函数
     */
    private static final Function<XJdbcSession<UUID, Object, DFrame>, ExceptionalFunction<LittleTree.IRecord, Integer>> persister = (
            sess) -> (record) -> {
                final var name = record.str("name");
                final var select = FT("select * from t_vertex where name='$0'", name);
                final var insert = FT("insert into t_vertex(name,Attributes) values ('$0','$1')", name, record.json());
                final var opt = sess.sql2maybe(select);
                return opt.isEmpty() ? sess.sql2execute2int(insert) : opt.map(e -> e.i4("id")).get();
            }; // insert or select

}
